package MicroSpring.beanFactoryPostProcessor.impl;

import MicroSpring.annotation.Bean;
import MicroSpring.annotation.Configuration;
import MicroSpring.annotation.Scope;
import MicroSpring.annotation.Value;
import MicroSpring.beanFactory.impl.BeanDefinition;
import MicroSpring.beanFactory.impl.DefaultListableBeanFactory;
import MicroSpring.beanFactoryPostProcessor.BeanFactoryPostProcessor;
import MicroSpring.beanPostProcessor.BeanPostProcessor;
import MicroSpring.beanPostProcessor.InstantiationAwareBeanPostProcessor;
import MicroSpring.builder.BeanDefinitionBuilder;
import MicroSpring.builder.BeanNameGenerator;
import MicroSpring.common.AnntationUtils;
import MicroSpring.common.BeanUtils;
import MicroSpring.common.IOUtils;
import MicroSpring.exception.BeansException;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;

/*
    工厂后处理器实现类: 需要扫描 @Component @Configuration @ComponentScan @Bean @Import,并且为他们实例化
    注册进bean工厂
 */
public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor {
    private final BeanNameGenerator generator=new BeanNameGenerator();
    private final BeanDefinitionBuilder beanDefinitionBuilder=new BeanDefinitionBuilder();
    private final static String SINGLETON="singelton";
    private final static String PROTOTYPE="prototype";
    private BeanPostProcessor importAwareBeanPostProcessor;
    private DefaultListableBeanFactory beanFactory;

    public BeanPostProcessor getImportAwareBeanPostProcessor(){
        return importAwareBeanPostProcessor;
    }


    public DefaultListableBeanFactory getBeanFactory() {
        return beanFactory;
    }

    public void setBeanFactory(DefaultListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(DefaultListableBeanFactory beanFactory) {
        //扫描含有以上注解的类,并且将他们注册进工厂
        scanAnnotationConfiguration(beanFactory);
        importAwareBeanPostProcessor=new ImportAwareBeanPostProcessor(beanFactory);
    }

    /**
     * * 扫描 @Component @Configuration @ComponentScan @Bean @Import
     *   几个不同的注解，应该分别进行处理
     * @param beanFactory
     */
    private void scanAnnotationConfiguration(DefaultListableBeanFactory beanFactory) {
        //先获取需要扫描的路径
        final String finalScanPath = beanFactory.getFINAL_SCAN_PATH();
        //扫描该路径下的所有类
        List<Class>totalClass= IOUtils.scanClassForTotalFilePath(finalScanPath);
        //获取到所有类之后，需要分类进行处理
        dealWithClassList(totalClass,beanFactory);
    }

    private void dealWithClassList(List<Class> totalClass, DefaultListableBeanFactory beanFactory) {
        for (Class clazz : totalClass) {
            Annotation[] annotations=AnntationUtils.getAnnotationByType(clazz);
            Map<Annotation, Method> methodAnnotationByType = AnntationUtils.getMethodAnnotationByType(clazz);
            beanFactory.addTypeAnnotations(clazz,annotations);
            beanFactory.addTypeMethodAnnotations(clazz,methodAnnotationByType);

            if (!checkAnnotationForClass(clazz))
                continue;
            if (AnntationUtils.isAnnotationOfComponent(clazz)){
                dealBeanWithComponent(clazz,beanFactory);
            }else if (clazz.isAnnotationPresent(Configuration.class)){
                dealBeanWithConfiguration(clazz,beanFactory);
            }else if (AnntationUtils.isAnnotationOfAspect(clazz)){
                dealBeanWithAspectBean(clazz,beanFactory);
            }


        }
    }

    private void dealBeanWithAspectBean(Class clazz, DefaultListableBeanFactory beanFactory) {
        prepareForAspect(beanFactory);
        BeanDefinition beanDefinition = beanDefinitionBuilder
                .setClass(clazz).setScope(SINGLETON).setRepetable(true).setIsAspect(true).build();

        String beanName=generator.generateBeanName(beanDefinition);
        beanFactory.registerBeanDefinition(beanName,beanDefinition);
    }

    private void prepareForAspect(DefaultListableBeanFactory beanFactory) {
        if (beanFactory.isOpenAspect()){
            return;
        }
        beanFactory.setOpenAspect(true);
    }

    /*
        处理Configuration注解
     */
    private void dealBeanWithConfiguration(Class clazz, DefaultListableBeanFactory beanFactory) {
        BeanDefinition beanDefinition = beanDefinitionBuilder.setClass(clazz).setScope(SINGLETON).setRepetable(true).build();
        String beanName=generator.generateBeanName(beanDefinition);
        beanFactory.registerBeanDefinition(beanName,beanDefinition);
        //寻找它的子bean
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (containAnnotationForMethod(method,Bean.class)){
                dealBeanWithBeanMethod(beanName,method,beanFactory);
            }
        }
    }
    //处理含有Bean注解的方法,并且要注意如果内部含有并且处理它们的参数
    private void dealBeanWithBeanMethodForConfiguration(Method method, DefaultListableBeanFactory beanFactory) {
        Class<?> beanType = method.getReturnType();
        Bean beanAnnotation = method.getAnnotation(Bean.class);
        String beanValue = beanAnnotation.value();
        BeanDefinition beanDefinition = beanDefinitionBuilder.setScope(SINGLETON).setClass(beanType).setRepetable(true).build();
        String beanName = generator.generateBeanName(beanValue, beanDefinition);
        beanFactory.registerBeanDefinition(beanName,beanDefinition);
    }

    /*
        处理Component注解
     */
    private void dealBeanWithComponent(Class clazz, DefaultListableBeanFactory beanFactory) {
        Scope scope = (Scope) clazz.getAnnotation(Scope.class);
        String scopeValue=SINGLETON;
        if (scope !=null){
            scopeValue=scope.value();
        }
        if (PROTOTYPE.equals(scopeValue)){
            return;
        }
        BeanDefinition beanDefinition = beanDefinitionBuilder.setClass(clazz).setScope(scopeValue).setRepetable(true).build();
        String beanName=generator.generateBeanName(beanDefinition);
        beanFactory.registerBeanDefinition(beanName,beanDefinition);
        //寻找它的子bean
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (containAnnotationForMethod(method,Bean.class)){
                dealBeanWithBeanMethod(beanName,method,beanFactory);
            }
        }
    }
    /*
        处理含有bean注解的方法,注册,并且将他们的添加进beanFactory,等待后续的依赖注入
     */
    private void dealBeanWithBeanMethod(String tBeanName,Method method, DefaultListableBeanFactory beanFactory) {
        Class<?> beanType = method.getReturnType();
        Bean beanAnnotation = method.getAnnotation(Bean.class);
        String beanValue = beanAnnotation.value();
        BeanDefinition beanDefinition = beanDefinitionBuilder.setScope(SINGLETON).setClass(beanType).setRepetable(true).build();
        String beanName = generator.generateBeanName(beanValue, beanDefinition);
        beanFactory.registerBeanDefinition(beanName,beanDefinition);
        //处理bean 方法内的参数
        if (method.getParameterTypes().length>0)
            beanFactory.addDealingInjectMethods(tBeanName,method);
    }

    private boolean containAnnotationForMethod(Method method, Class beanClass) {
        Annotation[] annotations = method.getAnnotations();
        if (annotations==null||annotations.length==0)
            return false;
        for (Annotation annotation : annotations) {
            if (beanClass==annotation.annotationType())
                return true;
        }
        return false;
    }


    /*
        检查目标类是否含有想要找的注解
     */
    private boolean checkAnnotationForClass(Class clazz) {
        if (clazz.isAnnotation())
            return false;
        if (null==clazz.getAnnotations()||clazz.getAnnotations().length==0)
            return false;
        return true;
    }




    /*
        通过componentScan的路径查找class列表
     */
    private List<Class> scanClassForScanPath(String queryScanPath,String CLASS_LODAR_PATH) {
        String finalScanPath=buildFinalScanPath(queryScanPath,CLASS_LODAR_PATH);
        return IOUtils.scanClassForTotalFilePath(finalScanPath);
    }

    private String buildFinalScanPath(String queryScanPath, String class_lodar_path) {
        queryScanPath=queryScanPath.replace(".",File.separator);
        return class_lodar_path+queryScanPath;
    }



    /*
        专属本工厂后处理器的静态内部类，处理实例化
     */
    private static class ImportAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor{

        private final DefaultListableBeanFactory beanFactory;

        public ImportAwareBeanPostProcessor(DefaultListableBeanFactory beanFactory) {
            this.beanFactory=beanFactory;
        }

        /*
            把原先依赖注入的对象，换成最终的代理对象。
        */
       @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
           if (beanFactory.getAspectBeans().containsKey(beanName)){
               Map<String, List<Field>> beansInjectBeans = beanFactory.getBeansInjectBeans();
               for (Map.Entry<String, List<Field>> entry : beansInjectBeans.entrySet()) {
                   String bName = entry.getKey();
                   List<Field> fields = entry.getValue();
                   for (Field f : fields) {
                       Class<?> fClazz = f.getType();
                       Object toCastBean = beanFactory.getBean(fClazz);
                       String injectedBeanName = beanFactory.getSingletonBeanNameOfType(fClazz);
                       if (injectedBeanName.equals(beanName)){
                           try {
                               Object injectedBean = beanFactory.getBean(bName);
                               f.setAccessible(true);
                               f.set(injectedBean,bean);
                           } catch (Exception e) {
                               throw new BeansException("注入代理对象异常");
                           }
                       }
                   }
               }
           }

           return bean;
        }

        /*
            依赖注入阶段做点事情
                如果这个对象的方法参数中含有已存在的bean或者存在value注解，那么帮它完成注入.
         */
        @Override
        public void postProcessProperties(Object bean, String methodInvokeBeanName) {
            Map<String, List<Method>> dealingInjectMethods = beanFactory.getDealingInjectMethods();
            if (dealingInjectMethods.containsKey(methodInvokeBeanName)){
                List<Method> methods = dealingInjectMethods.get(methodInvokeBeanName);
                for (Method method : methods) {
                    Parameter[] parameters = method.getParameters();
                    Object []ps=new Object[parameters.length];
                    for (int i = 0; i < parameters.length; i++) {
                        Object o=null;
                        if (beanFactory.containsBean(parameters[i].getType())){
                            o = beanFactory.getBean(parameters[i].getType());
                        }else if (parameters[i].isAnnotationPresent(Value.class)){
                            Value v = parameters[i].getDeclaredAnnotation(Value.class);
                            o=v.value();
                        }
                        if (o==null){
                            throw new BeansException("无法自动装配,找不到对应的bean");
                        }
                        ps[i]=o;
                    }
                    try {
                        Object invoke = method.invoke(bean, ps);
                        if (invoke!=null){
                            Object b1 = beanFactory.getBean(invoke.getClass());
                            if (b1.getClass() == invoke.getClass())
                                BeanUtils.copyProperties(invoke,b1);
                            else {
                                try {
                                    BeanUtils.copyPropertiesByMethod(invoke,b1);
                                } catch (NoSuchMethodException e) {
                                    throw new BeansException("注入切点值时出错.get/Set");
                                }
                            }
                        }
                        beanFactory.removeDealingMethod(methodInvokeBeanName);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}
